package Command; import java.nio.FloatBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.StringTokenizer; import java.util.TreeSet; import javax.media.opengl.GL2; import javax.swing.undo.UndoManager; import Common.Box2; import Common.Box3; import Common.Matrix3; import Common.Matrix4; import Common.Ray3; import Common.Segment3; import Common.Vector2f; import Common.Vector3f; import LDraw.Support.DispatchGroup; import LDraw.Support.ILDrawDragHandler; import LDraw.Support.LDrawDirective; import LDraw.Support.LDrawDragHandle; import LDraw.Support.LDrawGlobalFlag; import LDraw.Support.LDrawUtilities; import LDraw.Support.MatrixMath; import LDraw.Support.Range; import LDraw.Support.SelT; import LDraw.Support.type.CacheFlagsT; import Renderer.ILDrawCollector; import Renderer.ILDrawRenderer; import Renderer.LDrawRenderColorT; //============================================================================== // //File: LDrawLine.m // //Purpose: Line command. // Draws a line between two points. // // Line format: // 2 colour x1 y1 z1 x2 y2 z2 // // where // // * colour is a colour code: 0-15, 16, 24, 32-47, 256-511 // * x1, y1, z1 is the position of the first point // * x2, y2, z2 is the position of the second point // //Created by Allen Smith on 2/19/05. //Copyright (c) 2005. All rights reserved. //============================================================================== public class LDrawLine extends LDrawDrawableElement implements ILDrawDragHandler{ /** * @uml.property name="vertex1" * @uml.associationEnd multiplicity="(1 1)" */ Vector3f vertex1= Vector3f.getZeroVector3f(); /** * @uml.property name="vertex2" * @uml.associationEnd multiplicity="(1 1)" */ Vector3f vertex2= Vector3f.getZeroVector3f(); /** * @uml.property name="dragHandles" * @uml.associationEnd multiplicity="(0 -1)" elementType="LDraw.Support.LDrawDragHandle" */ ArrayList<LDrawDragHandle> dragHandles; //========== initWithLines:inRange:parentGroup: ================================ // // Purpose: Returns the LDraw directive based on lineFromFile, a single line // of LDraw code from a file. // // directive should have the format: // // 2 colour x1 y1 z1 x2 y2 z2 // //============================================================================== public LDrawLine initWithLines(ArrayList<String> lines, Range range, DispatchGroup parentGroup) { String workingLine = lines.get(range.getLocation()); String parsedField = null; Vector3f workingVertex = Vector3f.getZeroVector3f(); LDrawColor parsedColor = null; try { super.initWithLines(lines, range, parentGroup); } catch (Exception e1) { // TODO Auto-generated catch block e1.printStackTrace(); } //A malformed part could easily cause a string indexing error, which would // raise an exception. We don't want this to happen here. try { //Read in the line code and advance past it. StringTokenizer strTokenizer = new StringTokenizer(workingLine); parsedField = strTokenizer.nextToken(); //Only attempt to create the part if this is a valid line. if(Integer.parseInt(parsedField) == 2) { //Read in the color code. // (color) parsedField = strTokenizer.nextToken(); parsedColor = LDrawUtilities.parseColorFromField(parsedField); setLDrawColor(parsedColor); //Read Vertex 1. // (x1) parsedField = strTokenizer.nextToken(); workingVertex.setX(Float.parseFloat(parsedField)); // (y1) parsedField = strTokenizer.nextToken(); workingVertex.setY(Float.parseFloat(parsedField)); // (z1) parsedField = strTokenizer.nextToken(); workingVertex.setZ(Float.parseFloat(parsedField)); setVertex1(workingVertex); //Read Vertex 2. // (x2) parsedField = strTokenizer.nextToken(); workingVertex.setX(Float.parseFloat(parsedField)); // (y2) parsedField = strTokenizer.nextToken(); workingVertex.setY(Float.parseFloat(parsedField)); // (z2) parsedField = strTokenizer.nextToken(); workingVertex.setZ(Float.parseFloat(parsedField)); setVertex2(workingVertex); } else throw new Exception("BricksmithParseException: "+"Bad line syntax"); } catch(Exception e) { System.out.println(String.format("the line primitive %s was fatally invalid", lines.get(range.getLocation()))); System.out.println(String.format(" raised exception %s", e.getMessage())); } return this; }//end initWithLines:inRange: //========== drawSelf: =========================================================== // // Purpose: Draw this directive and its subdirectives by calling APIs on // the passed in renderer, then calling drawSelf on children. // // Notes: Lines use this message to get their drag handles drawn if // needed. They do not draw their actual GL primitive because that // has already been "collected" by some parent capable of // accumulating a mesh. // //================================================================================ public void drawSelf(GL2 gl2, ILDrawRenderer renderer) { revalCache(CacheFlagsT.DisplayList); if(hidden == false) { if(dragHandles!=null) { for(LDrawDragHandle handle : dragHandles) { handle.drawSelf(renderer); } } } }//end drawSelf: //========== collectSelf: ======================================================== // // Purpose: Collect self is called on each directive by its parents to // accumulate _mesh_ data into a display list for later drawing. // The collector protocol passed in is some object capable of // remembering the collectable data. // // Real GL primitives participate by passing their color and // geometry data to the collector. // //================================================================================ public void collectSelf(ILDrawCollector renderer) { revalCache(CacheFlagsT.DisplayList); if(color==null)return; if(hidden == false) { if (LDrawGlobalFlag.NO_LINE_DRWAING==false){ float v[] = { vertex1.getX(), vertex1.getY(), vertex1.getZ(), vertex2.getX(), vertex2.getY(), vertex2.getZ() }; float n[] = { 0, 0, 0 }; if(color.getColorCode() == LDrawColorT.LDrawCurrentColor){ renderer.drawLine(v, n, LDrawRenderColorT.LDrawRenderCurrentColor.getValue()); } else if(color.getColorCode() == LDrawColorT.LDrawEdgeColor){ renderer.drawLine(v, n, LDrawRenderColorT.LDrawRenderComplimentColor.getValue()); } else { float rgba[] = new float[4]; color.getColorRGBA(rgba); renderer.drawLine(v, n, rgba); } } } }//end collectSelf: //========== hitTest:transform:viewScale:boundsOnly:creditObject:hits: ======= // // Purpose: Tests the directive and any of its children for intersections // between the pickRay and the directive's drawn content. // //============================================================================== public void hitTest(Ray3 pickRay, Matrix4 transform, LDrawDirective creditObject, HashMap<LDrawDirective, Float> hits){ if(hidden == false) { Vector3f worldVertex1 = MatrixMath.V3MulPointByProjMatrix(vertex1, transform); Vector3f worldVertex2 = MatrixMath.V3MulPointByProjMatrix(vertex2, transform); Segment3 segment = new Segment3(worldVertex1, worldVertex2); float tolerance = 1.0f; FloatBuffer intersectDepth = FloatBuffer.allocate(1); boolean intersects = false; // Lines drawn to 1 pixel regardless of scale, so the pick tolerance must be // 1 pixel. We approximate this by relying on the view providing a 1 pixel // to 1 unit correspondence at 100%. tolerance *= 1.5; // allow a little fudge intersects = MatrixMath.V3RayIntersectsSegment(pickRay, segment, tolerance, intersectDepth); if(intersects) { LDrawUtilities.registerHitForObject(this, intersectDepth, creditObject, hits); } } } //========== boxTest:transform:boundsOnly:creditObject:hits: =================== // // Purpose: Check for intersections with screen-space bounding box. // //============================================================================== public boolean boxTest(Box2 bounds, Matrix4 transform, boolean boundsOnly, LDrawDirective creditObject, TreeSet<LDrawDirective> hits) { if(hidden == false) { Vector3f worldVertex1 = MatrixMath.V3MulPointByProjMatrix(vertex1, transform); Vector3f worldVertex2 = MatrixMath.V3MulPointByProjMatrix(vertex2, transform); Vector2f line[] = { MatrixMath.V2Make(worldVertex1.getX(),worldVertex1.getY()), MatrixMath.V2Make(worldVertex2.getX(),worldVertex2.getY()) }; if(MatrixMath.V2BoxIntersectsPolygon(bounds, line, 2)) { LDrawUtilities.registerHitForObject(this, creditObject, hits); if(creditObject != null) return true; } } return false; }//end boxTest:transform:boundsOnly:creditObject:hits: //========== depthTest:inBox:transform:creditObject:bestObject:bestDepth:======= // // Purpose: depthTest finds the closest primitive (in screen space) // overlapping a given point, as well as its device coordinate // depth. // //============================================================================== public void depthTest(Vector2f pt, Box2 bounds, Matrix4 transform, LDrawDirective creditObject, ArrayList<LDrawDirective> bestObject, FloatBuffer bestDepth) { if(hidden == false) { Vector3f worldVertex1 = MatrixMath.V3MulPointByProjMatrix(vertex1, transform); Vector3f worldVertex2 = MatrixMath.V3MulPointByProjMatrix(vertex2, transform); float tolerance2 = (bounds.getSize().getWidth()*bounds.getSize().getWidth()+bounds.getSize().getHeight()*bounds.getSize().getHeight())*0.25f; Vector3f probe = new Vector3f(pt.getX() , pt.getY(), bestDepth.get(0)); if(MatrixMath.DepthOnLineSegment(worldVertex1,worldVertex2,tolerance2, probe)) { if(probe.getZ() <= bestDepth.get(0)) { bestDepth.put(0, probe.getZ()); bestObject.add((LDrawDirective) (creditObject!=null ? creditObject : this)); } } if(dragHandles!=null) { for(LDrawDragHandle handle : dragHandles) { handle.depthTest(pt, bounds, transform, creditObject, bestObject, bestDepth); } } } }//end depthTest:inBox:transform:creditObject:bestObject:bestDepth: //========== write ============================================================= // // Purpose: Returns a line that can be written out to a file. // Line format: // 2 colour x1 y1 z1 x2 y2 z2 // //============================================================================== public String write() { return String.format( "2 %s %s %s %s %s %s %s", LDrawUtilities.outputStringForColor(color), LDrawUtilities.outputStringForFloat(vertex1.getX()), LDrawUtilities.outputStringForFloat(vertex1.getY()), LDrawUtilities.outputStringForFloat(vertex1.getZ()), LDrawUtilities.outputStringForFloat(vertex2.getX()), LDrawUtilities.outputStringForFloat(vertex2.getY()), LDrawUtilities.outputStringForFloat(vertex2.getZ()) ); }//end write //========== browsingDescription =============================================== // // Purpose: Returns a representation of the directive as a short string // which can be presented to the user. // //============================================================================== public String browsingDescription() { return "Line"; }//end browsingDescription //========== iconName ========================================================== // // Purpose: Returns the name of image file used to display this kind of // object, or null if there is no icon. // //============================================================================== public String iconName() { return "Line"; }//end iconName //========== inspectorClassName ================================================ // // Purpose: Returns the name of the class used to inspect this one. // //============================================================================== public String inspectorClassName() { return "InspectionLine"; }//end inspectorClassName // #pragma mark - // #pragma mark ACCESSORS // #pragma mark - //========== boundingBox3 ====================================================== // // Purpose: Returns the minimum and maximum points of the box which // perfectly contains this object. // //============================================================================== public Box3 boundingBox3() { revalCache(CacheFlagsT.CacheFlagBounds); if (hidden == true) return Box3.getInvalidBox(); Box3 bounds =MatrixMath.V3BoundsFromPoints(vertex1, vertex2); return bounds; }//end boundingBox3 //========== position ========================================================== // // Purpose: Returns some position for the element. This is used by // drag-and-drop. This is not necessarily human-usable information. // //============================================================================== public Vector3f position() { return vertex1; }//end position //========== vertex1 =========================================================== // // Purpose: Returns the line's start point. // //============================================================================== public Vector3f vertex1() { return vertex1; }//end vertex1 //========== vertex2 =========================================================== // // Purpose: Returns the line's end point. // //============================================================================== public Vector3f vertex2() { return vertex2; }//end vertex2 // #pragma mark - //========== setSelected: ====================================================== // // Purpose: Somebody make this a protocol method. // //============================================================================== public void setSelected(boolean flag) { super.setSelected(flag); if(flag == true) { LDrawDragHandle handle1 = new LDrawDragHandle(); handle1.initWithTag(1, vertex1); LDrawDragHandle handle2 =new LDrawDragHandle(); handle1.initWithTag(2, vertex2); handle1.setTarget(this); handle2.setTarget(this); handle1.setAction(SelT.DragHandleChanged); handle2.setAction(SelT.DragHandleChanged); dragHandles = new ArrayList<LDrawDragHandle>(); dragHandles.add(handle1); dragHandles.add(handle2); } else { dragHandles = null; } }//end setSelected: //========== setVertex1: ======================================================= // // Purpose: Sets the line's start point. // //============================================================================== /** * @param newVertex * @uml.property name="vertex1" */ public void setVertex1(Vector3f newVertex) { vertex1.set(newVertex); invalCache(CacheFlagsT.CacheFlagBounds); invalCache(CacheFlagsT.DisplayList); if(dragHandles!=null) { dragHandles.get(0).setPosition(newVertex, false); } if(enclosingDirective()!=null) enclosingDirective().setVertexesNeedRebuilding(); }//end setVertex1: //========== setVertex2: ======================================================= // // Purpose: Sets the line's end point. // //============================================================================== /** * @param newVertex * @uml.property name="vertex2" */ public void setVertex2(Vector3f newVertex) { vertex2.set(newVertex); invalCache(CacheFlagsT.CacheFlagBounds); invalCache(CacheFlagsT.DisplayList); if(dragHandles!=null) { dragHandles.get(1).setPosition(newVertex, false); } if(enclosingDirective()!=null) enclosingDirective().setVertexesNeedRebuilding(); }//end setVertex2: // #pragma mark - // #pragma mark ACTIONS // #pragma mark - //========== dragHandleChanged: ================================================ // // Purpose: One of the drag handles on our vertexes has changed. // //============================================================================== public void dragHandleChanged(LDrawDragHandle sender) { LDrawDragHandle handle = (LDrawDragHandle )sender; Vector3f newPosition =handle.position(); int vertexNumber = handle.tag(); switch(vertexNumber) { case 1: setVertex1(newPosition); break; case 2: setVertex2(newPosition); break; } }//end dragHandleChanged: //========== moveBy: ============================================================ // // Purpose: Moves the receiver in the specified direction. // //============================================================================== public void moveBy(Vector3f moveVector) { Vector3f newVertex1 = MatrixMath.V3Add(vertex1, moveVector); Vector3f newVertex2 = MatrixMath.V3Add(vertex2, moveVector); setVertex1(newVertex1); setVertex2(newVertex2); }//end moveBy: // #pragma mark - // #pragma mark UTILITIES // #pragma mark - //========== flattenIntoLines:triangles:quadrilaterals:other:currentColor: ===== // // Purpose: Appends the directive into the appropriate container. // //============================================================================== public void flattenIntoLines(ArrayList<LDrawLine> lines, ArrayList<LDrawTriangle> triangles, ArrayList<LDrawQuadrilateral> quadriaterals, ArrayList<LDrawDirective> everythingElse, LDrawColor parentColor, Matrix4 transform, Matrix3 normalTransform, boolean recursive) { super.flattenIntoLines(lines, triangles, quadriaterals, everythingElse, parentColor, transform, normalTransform, recursive); vertex1 = MatrixMath.V3MulPointByProjMatrix(vertex1, transform); vertex2 = MatrixMath.V3MulPointByProjMatrix(vertex2, transform); lines.add(this); }//end flattenIntoLines:triangles:quadrilaterals:other:currentColor: //========== registerUndoActions =============================================== // // Purpose: Registers the undo actions that are unique to this subclass, // not to any superclass. // //============================================================================== public void registerUndoActions(UndoManager undoManager) { super.registerUndoActions(undoManager); //todo // [[undoManager prepareWithInvocationTarget:self] setVertex2:vertex2]); // [[undoManager prepareWithInvocationTarget:self] setVertex1:vertex1]); // [undoManager setActionName:NSLocalizedString(@"UndoAttributesLine", null)); }//end registerUndoActions: }